package ws08;

import com.hp.hpl.jena.db.IDBConnection;
import com.hp.hpl.jena.ontology.OntClass;
import com.hp.hpl.jena.rdf.model.Model;
import com.hp.hpl.jena.db.DBConnection;
import com.hp.hpl.jena.ontology.Individual;
import com.hp.hpl.jena.ontology.OntModel;
import com.hp.hpl.jena.ontology.OntModelSpec;
import com.hp.hpl.jena.rdf.model.ModelFactory;
import com.hp.hpl.jena.rdf.model.ModelMaker;
import com.hp.hpl.jena.rdf.model.Property;
import com.hp.hpl.jena.rdf.model.Resource;
import com.hp.hpl.jena.util.FileManager;
import com.hp.hpl.jena.util.iterator.ExtendedIterator;
import com.thoughtworks.qdox.JavaDocBuilder;
import com.thoughtworks.qdox.model.DocletTag;
import com.thoughtworks.qdox.model.JavaClass;
import com.thoughtworks.qdox.model.JavaField;
import com.thoughtworks.qdox.model.JavaMethod;
import com.thoughtworks.qdox.model.JavaParameter;
import java.io.File;
import java.io.FileFilter;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.io.IOException;
import java.text.DecimalFormat;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.lucene.index.CorruptIndexException;
import org.apache.lucene.search.Hits;
import org.apache.lucene.search.Query;
import org.apache.lucene.document.Field;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.document.Document;
import org.apache.lucene.store.RAMDirectory;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.queryParser.QueryParser;
import org.apache.lucene.queryParser.ParseException;
import org.apache.lucene.analysis.standard.StandardAnalyzer;

/**
 *
 * @author Filipe
 */
public class IndexEngine {

    public static final String NAME_SEPARATOR = "_";
    // database URL
    static String M_DB_URL = "jdbc:mysql://localhost:3309/wsdb";
    // User name
    static String M_DB_USER = "ws";
    // Password
    static String M_DB_PASSWD = "ws";
    // Database engine name
    static String M_DB = "MySQL";
    // JDBC driver
    static String M_DBDRIVER_CLASS = "com.mysql.jdbc.Driver";
    static String URI = "http://www.owl-ontologies.com/javadoc_renteBK2008WS.owl#";
    static String PREFIX = "PREFIX nosso: <" + URI + ">";
    static String inputFileName = "JavadocOntologia.owl";    // Properties
    static String bodeQuery = "SELECT ?FileName" +
            "WHERE ?x rdfs:label 'metodo'" +
            "?file ?nosso:temClasse ?c" +
            "?c ?nosso:temMetodo ?y " +
            "?file rdfs:type nosso:Ficheiro" +
            "?file nosso:nome_ficheiro XXXXX" +
            "?y rdfs:type ?x" +
            "?y rdfs:label 'XPTO'";
    /*
    "SELECT ?FileName" +
    "WHERE ?x rdfs:label 'metodo'
    ?file 
    ?file ?p ?y     --->            ?file ?temClasse ?c
     *      ?file rdfs:type nosso:Ficheiro     ?c ?temMetodo ?y 
     *      ?file nosso:nome_ficheiro
    ?y rdfs:type ?x
    ?y rdfs:label 'XPTO'
    "
    
     */
    //Props de FICHEIROS
    static Property propCaminhoFicheiro;
    static Property propNomeFicheiro;
    static Property propDataModific;
    static Property propTamanho;
    static Property proptemClasse;
    //Props de CLASSES
    static Property propAutor;
    static Property propDeprecated;
    static Property propDescricao;
    static Property propDesde_versao;
    static Property prophasImport;
    static Property prophasPackage;
    static Property propNomeDaClasse;
    static Property proptemMetodo;
    static Property proptemVar;
    static Property propThrows_exception;
    static Property propVer_tambem;
    static Property propVersao;
    //Props dos METODOS   
    static Property propNome_do_metodo;
    static Property proptemParametros;
    static Property proptemVars;
    static Property propValor_de_retorno;
    static Property propThrows_exception_metodo;
    static Property propDeprecated_metodo;
    static Property propDescricaoMetodo;
    //Props da Package
    static Property proppackage_name;
    //Props das Variaveis
    static Property propNome_var;
    static Property propScope_var;
    static Property propTipo_var;
    //Props dos Parametros de entrada
    static Property propDescricao_param_entrada;
    static Property propNome_param_entrada;
    static RAMDirectory idx;
    public OntModel ontModel;
    private ModelMaker maker;
    private IDBConnection conn;
    public long numJavasInDir = 1;
    public long currentJavas = 0;
    private String currentPercentage = "0";
    private Model base;
    private String propOntClassFicheiro;
    private String propOntClassPackage;
    private String propOntClassClasse;
    private String propOntClassMetodo;
    private String propOntClassVars;
    private String propOntClassParam;
    private int countFilesAdded;
    private boolean podeIndexar = true;
    boolean indexando = false;
    private GUI gui;

    public IndexEngine(GUI gui) {
        this.gui = gui;
    }

    public OntModelSpec getModelSpec(ModelMaker maker) {
        // create a spec for the new ont model that will use no inference over models
        // made by the given maker (which is where we get the persistent models from)
        OntModelSpec spec = new OntModelSpec(OntModelSpec.OWL_MEM);
        spec.setImportModelMaker(maker);

        return spec;
    }

    public boolean inicializa() throws CorruptIndexException, IOException {
        boolean ret = true;
        idx = new RAMDirectory();
        try {
            // load the the driver class
            Class.forName(M_DBDRIVER_CLASS);
        } catch (ClassNotFoundException ex) {
            ex.printStackTrace();
        }
        // create a database connection

        conn = new DBConnection(M_DB_URL, M_DB_USER, M_DB_PASSWD, M_DB);

        // create a model maker with the given connection parameters
        maker = ModelFactory.createModelRDBMaker(conn);

        base = maker.openModel("MyWSModel");

        ontModel = ModelFactory.createOntologyModel(getModelSpec(maker), base);


        if (ontModel.isEmpty()) {
            System.out.println("MODELO VAZIO");
            base = maker.createModel("MyWSModel");

            ontModel = ModelFactory.createOntologyModel(getModelSpec(maker), base);

            // now load the source document, which will also load any imports

            InputStream in = FileManager.get().open(inputFileName);
            if (in == null) {
                throw new IllegalArgumentException("File: " + inputFileName + " not found");
            }
            // read the RDF/XML file
            ontModel.read(in, "");
        } else {
            System.out.println("EXISTE MODELO NAO VAZIO NA BD");
        }

        //Criacao de propriedades:

        /*FICHEIRO*/
        propOntClassFicheiro = URI + "Ficheiro";
        propCaminhoFicheiro = ontModel.getProperty(URI, "Caminho");
        propDataModific = ontModel.getProperty(URI, "Data_de_modificacao");
        propNomeFicheiro = ontModel.getProperty(URI, "Nome_ficheiro");
        propTamanho = ontModel.getProperty(URI, "Tamanho");
        proptemClasse = ontModel.getProperty(URI, "temClasse");

        /*CLASSE*/
        propOntClassClasse = URI + "Classe";
        propNomeDaClasse = ontModel.getProperty(URI, "Nome_da_classe");
        prophasPackage = ontModel.getProperty(URI, "hasPackage");
        proptemMetodo = ontModel.getProperty(URI, "temMetodo");
        proptemVar = ontModel.getProperty(URI, "temVar");
        propAutor = ontModel.getProperty(URI, "Autor");
        propDesde_versao = ontModel.getProperty(URI, "Desde_versao");
        propVer_tambem = ontModel.getProperty(URI, "Ver_tambem");
        propThrows_exception = ontModel.getProperty(URI, "Throws_exception");
        propVersao = ontModel.getProperty(URI, "Versao");
        propDeprecated = ontModel.getProperty(URI, "Deprecated");
        prophasImport = ontModel.getProperty(URI, "hasImport");
        propDescricao = ontModel.getProperty(URI, "Descricao");


        /*METODO*/
        propOntClassMetodo = URI + "Metodo";
        propNome_do_metodo = ontModel.getProperty(URI, "Nome_do_metodo");
        propDescricao_param_entrada = ontModel.getProperty(URI, "Descricao_param_entrada");
        proptemParametros = ontModel.getProperty(URI, "temParametros");
        propValor_de_retorno = ontModel.getProperty(URI, "Valor_de_retorno");
        propThrows_exception_metodo = ontModel.getProperty(URI, "Throws_exception");
        propDeprecated_metodo = ontModel.getProperty(URI, "Deprecated");
        propDescricaoMetodo = ontModel.getProperty(URI, "Descricao_do_metodo");


        /*VARS*/
        propOntClassVars = URI + "Variavel";
        propNome_var = ontModel.getProperty(URI, "Nome_var");
        propScope_var = ontModel.getProperty(URI, "Scope_var");
        propTipo_var = ontModel.getProperty(URI, "Tipo_var");

        /*PARAMETRO*/
        propOntClassParam = URI + "Parametros_de_entrada";
        propNome_param_entrada = ontModel.getProperty(URI, "Nome_param_entrada");
        propDescricao_param_entrada = ontModel.getProperty(URI, "Descricao_param_entrada");

        /*PACKAGE*/
        propOntClassPackage = URI + "Package";
        proppackage_name = ontModel.getProperty(URI, "package_name");

        // write it to standard out
        //NUNO COMENTOU
        //ontModel.write(System.out, "N-TRIPLES");

        return ret;
    }

    public long countJavas(File folder) {
        List<File> crawled = new ArrayList<File>();
        long n = 0;
        crawled.add(folder);
        while (!crawled.isEmpty()) {
            File actual = crawled.remove(0);
            if (actual.isDirectory()) {
//                System.out.println("A expandir:" + actual);
                File[] listFiles = actual.listFiles(new FileFilter() {

                    public boolean accept(File pathname) {
                        return pathname.isDirectory() || pathname.getName().endsWith(".java");
                    }
                });
                for (File file : listFiles) {
                    crawled.add(file);
                }
            } else {
                if (actual.getName().contains(".java")) {
                    n++;
                }
            }
        }
        return n;
    }

    public long getSize(File folder) {
        long folderSize = 0;
        if (folder.isDirectory()) {
            File[] filelist = folder.listFiles();
            for (int i = 0; i < filelist.length; i++) {
                System.out.println("XXXX" + filelist[i].getName());
                if (filelist[i].getPath().contains(".java")) {
                    folderSize += getSize(filelist[i]);
                }
            }
            return folderSize;
        } else {
            return folder.length();
        }
    }

    public boolean indexa(String path) throws Exception {
        Document docClass = new Document();
        Document docMethod = new Document();

        JavaDocBuilder jdb;
        OntClass ontClass;
        Individual metodo;
        Individual classe;
        Individual variavel;
        Individual ficheiro;
        Individual paramEntrada;
        Individual Package;
        List<File> crawled = new ArrayList<File>();
        File f = new File(path);
        numJavasInDir = countJavas(f);
        currentPercentage = "0";
        currentJavas = 0;
        setCountFilesAdded(0);
        long length;
        crawled.add(f);
        SimpleDateFormat sdf = new SimpleDateFormat();
        Date data;
        String uriIdentifier;
        int filenameCounter = 0;
        IndexWriter writer = new IndexWriter(idx, new StandardAnalyzer(), true);
        Document doc;
        DecimalFormat df = new DecimalFormat("0");
        String idFicheiro = "";
        String idClasse = "";
        String idMetodo = "";
        String idPackage = "";
        String idVar = "";
        String idParam = "";
        System.out.println(" TOTAL javas:  " + countJavas(f) + " Javas");

        ontModel.removeAll();
        gui.setTextIndexOut("Dados previamente indexados removidos");
        if (ontModel.isEmpty()) {
            System.out.println("apaguei tudo! ahaha!");
        }

        base = maker.createModel("MyWSModel");

        ontModel = ModelFactory.createOntologyModel(getModelSpec(maker), base);

        // now load the source document, which will also load any imports

        InputStream in = FileManager.get().open(inputFileName);
        if (in == null) {
            throw new IllegalArgumentException("File: " + inputFileName + " not found");
        }
        // read the RDF/XML file
        ontModel.read(in, "");
        gui.setTextIndexOut("Indexação inicializada");
        long tempo = System.currentTimeMillis();
        while (!crawled.isEmpty() && podeIndexar) {
            // System.out.println(" current javas:  " + currentJavas + " total " + numJavasInDir);
            try {
                File actual = crawled.remove(0);
                if (actual.isDirectory()) {

                    //System.out.println("A expandir:" + actual);
                    File[] listFiles = actual.listFiles(new FileFilter() {

                        public boolean accept(File pathname) {
                            return pathname.isDirectory() || pathname.getName().endsWith(".java");
                        }
                    });
                    for (File file : listFiles) {
                        crawled.add(file);
                    }
                } else {
                    jdb = new JavaDocBuilder();
                    try {
                        jdb.addSource(actual);
                    } catch (Exception e) {
                        System.out.println("Nao acolhe:\t" + actual);
                    }
                    System.out.println("adding: " + actual);
                    gui.setTextIndexOut("Adicionando: \"" + actual+"\"");
                    setCountFilesAdded(getCountFilesAdded() + 1);
                    doc = new Document();
                    uriIdentifier = URI + "WS_" + filenameCounter++;
                    /*aqui*/

                    //JENA

                    ontClass = ontModel.getOntClass(propOntClassFicheiro);

                    ficheiro = ontClass.createIndividual(uriIdentifier);
                    ficheiro.addProperty(propCaminhoFicheiro, actual.getAbsoluteFile().getPath().toLowerCase());
                    ficheiro.addProperty(propNomeFicheiro, actual.getName());

                    //////////////
                    //////////// isto ainda nao ta a por path po lucene!!
                    doc.add(new Field("file_name", actual.getAbsoluteFile().getPath(), Field.Store.YES, Field.Index.TOKENIZED));


                    data = new Date(actual.getAbsoluteFile().lastModified());

                    ficheiro.addProperty(propDataModific, sdf.format(data));
                    doc.add(new Field("data", data.toString(), Field.Store.YES, Field.Index.TOKENIZED));
                    length = actual.getAbsoluteFile().length();

                    currentPercentage = df.format(((double) currentJavas++ / numJavasInDir) * 100);
                    gui.setPercentageLabel(currentPercentage);
                    ficheiro.addProperty(propTamanho, length + "");
                    doc.add(new Field("size", length + "", Field.Store.YES, Field.Index.TOKENIZED));

                    /*CLASSE*/
                    JavaClass[] classes = jdb.getClasses();
                    int classCounter = 0;
                    for (JavaClass javaClass : classes) {

                        idClasse = uriIdentifier + encode(javaClass.getName()) + classCounter++;
                        docClass = new Document();
                        ontClass = ontModel.getOntClass(propOntClassClasse);
                        classe = ontClass.createIndividual(idClasse);
                        ficheiro.addProperty(proptemClasse, classe);
                        doc.add(new Field("temClasse", idClasse, Field.Store.YES, Field.Index.TOKENIZED));
                        classe.addProperty(propNomeDaClasse, javaClass.getName());
                        docClass.add(new Field("className", javaClass.getName(), Field.Store.YES, Field.Index.TOKENIZED));
                        if (javaClass.getComment() != null) {
                            classe.addProperty(propDescricao, javaClass.getComment());
                            docClass.add(new Field("Descricao", javaClass.getComment(), Field.Store.YES, Field.Index.TOKENIZED));
                        }



                        /*PACKAGE*/
                        int packageCounter = 0;
                        if (javaClass.getPackage() != null) {
                            idPackage = idClasse + javaClass.getPackage() + packageCounter++;
                            ontClass = ontModel.getOntClass(propOntClassPackage);
                            Package = ontClass.createIndividual(idPackage);
                            classe.addProperty(prophasPackage, Package);
                            docClass.add(new Field("temPackage", idPackage, Field.Store.YES, Field.Index.TOKENIZED));
                            Package.addProperty(proppackage_name, javaClass.getPackage());
                        }


                        /*TAGS*/
                        DocletTag[] dt = javaClass.getTags();
                        if (dt.length > 0) {
                            for (DocletTag docletTag : dt) {
                                if (docletTag.getName().equalsIgnoreCase("author")) {
                                    classe.addProperty(propAutor, docletTag.getValue());
                                    docClass.add(new Field("Autor", docletTag.getValue(), Field.Store.YES, Field.Index.TOKENIZED));
                                } else if (docletTag.getName().equalsIgnoreCase("since")) {
                                    classe.addProperty(propDesde_versao, docletTag.getValue());
                                    docClass.add(new Field("since", docletTag.getValue(), Field.Store.YES, Field.Index.TOKENIZED));
                                } else if (docletTag.getName().equalsIgnoreCase("see")) {
                                    classe.addProperty(propVer_tambem, docletTag.getValue());
                                    docClass.add(new Field("verTambem", docletTag.getValue(), Field.Store.YES, Field.Index.TOKENIZED));
                                } else if (docletTag.getName().equalsIgnoreCase("version")) {
                                    classe.addProperty(propVersao, docletTag.getValue());
                                    docClass.add(new Field("version", docletTag.getValue(), Field.Store.YES, Field.Index.TOKENIZED));
                                } else if (docletTag.getName().equalsIgnoreCase("deprecated")) {
                                    classe.addProperty(propDeprecated, docletTag.getName());
                                    docClass.add(new Field("deprecated", docletTag.getValue(), Field.Store.YES, Field.Index.TOKENIZED));
                                } else if (docletTag.getName().equalsIgnoreCase("throws") || docletTag.getName().equalsIgnoreCase("exception")) {
                                    classe.addProperty(propThrows_exception, docletTag.getValue());
                                    docClass.add(new Field("throws", docletTag.getValue(), Field.Store.YES, Field.Index.TOKENIZED));
                                }
                            }
                        }
                        /*VARIAVEIS GLOBAIS*/
                        int varCounter = 0;
                        JavaField[] fields = javaClass.getFields();
                        if (fields.length > 0) {
                            for (JavaField javaField : fields) {
                                idVar = idPackage + encode(javaField.getName()) + varCounter++;
                                ontClass = ontModel.getOntClass(propOntClassVars);
                                variavel = ontClass.createIndividual(idVar);
                                variavel.addProperty(propNome_var, javaField.getName());
                                docClass.add(new Field("varName", javaField.getName(), Field.Store.YES, Field.Index.TOKENIZED));
                                variavel.addProperty(propTipo_var, javaField.getType().toString());
                                classe.addProperty(proptemVar, variavel);
                                docClass.add(new Field("temVar", idVar, Field.Store.YES, Field.Index.TOKENIZED));

                            }
                        }
                        //
                        // INFO DOS METODOS
                        //
                        JavaMethod[] methods = javaClass.getMethods();

                        //
                        //Usar no campo de pesquisa por exemplo a frase: "recebe String" temos que procurar
                        //nos valores de entrada do metodo ou classe. Mesma coisa para "devolve string", procura
                        //nos valores de saida do metodo.
                        // Isso já é a parte de interpretar o que é pedido no campo de entrada.
                        // tem que ter a inteligencia suficiente para perceber o que é pedido.

                        int methodCounter = 0;
                        for (JavaMethod javaMethod : methods) {
                            idMetodo = idPackage + encode(javaMethod.getName()) + methodCounter++;
                            docMethod = new Document();
                            ontClass = ontModel.getOntClass(propOntClassMetodo);
                            metodo = ontClass.createIndividual(idMetodo);

                            classe.addProperty(proptemMetodo, metodo);
                            docClass.add(new Field("temMetodo", idMetodo, Field.Store.YES, Field.Index.TOKENIZED));
                            metodo.addProperty(propNome_do_metodo, javaMethod.getName());
                            docMethod.add(new Field("methodName", javaMethod.getName(), Field.Store.YES, Field.Index.TOKENIZED));
                            if (javaMethod.getComment() != null) {
                                metodo.addProperty(propDescricaoMetodo, javaMethod.getComment());
                                docMethod.add(new Field("DescricaoMetodo", javaMethod.getComment(), Field.Store.YES, Field.Index.TOKENIZED));
                            }
                            DocletTag[] tags = javaMethod.getTags();
                            for (DocletTag docletTag : tags) {
                                if (docletTag.getName().equalsIgnoreCase("throws") || docletTag.getName().equalsIgnoreCase("exception")) {
                                    metodo.addProperty(propThrows_exception_metodo, docletTag.getValue().split(" ")[0].trim());
                                    docMethod.add(new Field("methodThrows", docletTag.getValue().split(" ")[0].trim(), Field.Store.YES, Field.Index.TOKENIZED));
                                }
                                if (docletTag.getName().equalsIgnoreCase("deprecated")) {
                                    metodo.addProperty(propDeprecated_metodo, docletTag.getName());
                                    docMethod.add(new Field("deprecatedMethod", docletTag.getValue().split(" ")[0].trim(), Field.Store.YES, Field.Index.TOKENIZED));
                                }
                            }

                            JavaParameter[] methodParams = javaMethod.getParameters();
                            int paramCounter = 0;
                            for (JavaParameter javaParameter : methodParams) {
                                idParam = idMetodo + javaParameter.getName() + paramCounter++;
                                ontClass = ontModel.getOntClass(propOntClassParam);
                                paramEntrada = ontClass.createIndividual(idParam);
                                metodo.addProperty(proptemParametros, paramEntrada);
                                paramEntrada.addProperty(propNome_param_entrada, javaParameter.getName());
                                docMethod.add(new Field("paramEntrada", javaParameter.getName(), Field.Store.YES, Field.Index.TOKENIZED));
                                paramEntrada.addProperty(propDescricao_param_entrada, getDescricaoParam(javaParameter.getParentMethod().toString(), javaParameter.getName()));
                                docMethod.add(new Field("paramEntradaDesc", getDescricaoParam(javaParameter.getParentMethod().toString(), javaParameter.getName()), Field.Store.YES, Field.Index.TOKENIZED));
                            }
                            metodo.addProperty(propValor_de_retorno, javaMethod.getReturns() + "");
                            docMethod.add(new Field("methodReturns", javaMethod.getReturns() + "", Field.Store.YES, Field.Index.TOKENIZED));
                        }
                    }
                    writer.addDocument(docClass);
                    writer.addDocument(docMethod);
                    writer.addDocument(doc);
                }
            } catch (Exception e) {
                System.out.println("ABORT");
                gui.setPercentageLabel("0");
                gui.setTextIndexOut("\nIndexação interrompida");
                podeIndexar = true;
                currentPercentage = "0";
                indexando = false;
                return false;
            }
        }
        gui.setPercentageLabel("100");
        gui.setTextIndexOut("\nIndexação terminada. " + currentJavas + " ficheiros indexados");
        gui.setTextIndexOut("Tempo decorrido: " + df.format((System.currentTimeMillis() - tempo)/1000)+ " segundos");
        writer.optimize();
        writer.close();
        indexando = false;
        numJavasInDir = 0;
        currentPercentage = "0";
        currentJavas = 0;
        return true;
    }

    private static String getDescricaoParam(String source, String paramName) {
        //System.out.println("\n\n\n\n BAH: \n" + source + "\nBAAAAHHH\n\n\n\n");
        int pos = source.indexOf(paramName);
        //System.out.println("ESTA EM: " + pos);
        String a1 = source.substring(pos + paramName.length() + 1);
        //System.out.println("TEXTO:" + a1);
        String a1a[] = a1.split("\n");
        //System.out.println("FINAL: " + a1a[0]);
        return a1a[0];
    }

    void abortIndexing() {
        podeIndexar = false;
        ontModel.removeAll();

    }

    private String encode(String name) {
        if (name.startsWith("U")) {
            name = "*" + name;
        }
        return name;
    }    //AH BENFICA

    public long getTotalFolderSize() {
        return numJavasInDir;
    }

    public long getCurrentSize() {
        return currentJavas;
    }

    public String getCurrentPercentage() {
        return currentPercentage;
    }

    public int getCountFilesAdded() {
        return countFilesAdded;
    }

    public void setCountFilesAdded(int countFilesAdded) {
        this.countFilesAdded = countFilesAdded;
    }
}
